import pygame
import random
import math


class AuroraLogo:
    def __init__(self, screen_width, y_pos, speed=1.5):
        """类的构造函数，初始化方法，"""
        self.screen_width = screen_width
        self.y_pos = y_pos #存储Logo在屏幕垂直方向（Y轴）上的固定位置。
        self.speed = speed
        self.width = 400
        self.height = 180
        self.center_x = 0
        self.reset()

        self.info_font = pygame.font.SysFont("Simsun", 22, bold=True)

        #将姓名和学号的颜色改为黑色
       #true，表示要抗锯齿
        self.name_surf = self.info_font.render("姓名: 薛琦", True, (0, 0, 0))  # 黑色
        self.id_surf = self.info_font.render("学号: 0241122033", True, (0, 0, 0))  # 黑色

        # 创建一个空列表，用来存储所有主要的、大的能量云粒子
        self.wisps = []  # 主要的“能量云”
        # 创建一个空列表，用来存储环绕的、小的星环粒子
        self.rings = []  # 环绕的“星环”
        # 初始化一个计时器，用于控制动画节奏
        self.time = 0
        #修改点 2：加深主能量云的颜色

        # 为主能量云和环绕粒子定义不同亮度的调色板
        # 主能量云的颜色，更亮一些
        self.main_palette = [(0, 128, 255), (220, 20, 60)]  # 亮蓝色 和 绯红色
        # 星环粒子的颜色，更暗一些
        self.dark_palette = [(0, 100, 200), (180, 50, 90)]  # 深蓝, 深红 (保持不变)

        for i in range(10): # 创建10个主要的能量云
            # 将一个字典添加到 wisps 列表中，每个字典代表一个能量云粒子
            self.wisps.append({
                'rel_pos': pygame.Vector2(random.uniform(-self.width / 2.5, self.width / 2.5),
                                          random.uniform(-self.height / 2.5, self.height / 2.5)),
                # 相对于Logo中心的随机初始位置
                """1.pygame.Vector2 是 Pygame 库中一个非常有用的类，专门用来表示二维向量或坐标点。
                2.random.uniform(a, b),Python 的 random 模块中的一个函数。它的作用是生成一个在 a 和 b 之间的随机浮点数（也就是小数）。
                3.self.width / 2.5: 将这个宽度除以 2.5，得到一个较小的值。-self.width / 2.5: 这个值的负数形式。
                4.以0为中心的范围内生成一个随机的X坐标。范围是从 -宽度/2.5 到 +宽度/2.5。
                """
                'base_radius': random.uniform(8, 15),# 基础半径
                'vel': pygame.Vector2(random.uniform(-0.3, 0.3), random.uniform(-0.3, 0.3)),
                # 随机的移动速度和方向
                'pulse_time': random.uniform(0, 2 * math.pi), # 用于脉冲效果的随机初始相位
                'breath_time': random.uniform(0, 2 * math.pi), # 用于呼吸效果的随机初始相位
                'color': random.choice(self.main_palette) # 从主调色板中随机选一个颜色
            })

        # 创建两个环绕的星环
        # 创建第一个星环：半径范围60-80, 旋转速度0.006, 包含40个粒子
        self.rings.append(self.create_ring(60, 80, 0.006, 40))
        # 创建第二个星环：半径范围100-120, 旋转速度-0.004(反向旋转), 包含60个粒子
        self.rings.append(self.create_ring(100, 120, -0.004, 60))

        # 创建一个空字典，用作缓存。避免重复创建相同大小和颜色的光晕图像，提高性能
        self.glow_cache = {}

    # 定义一个方法，用来创建一个带光晕效果的圆形图像
    def create_aurora_glow(self, radius, color):
        """
        最终版 - 动态创建一个无分层的、中心明亮的柔和光晕 Surface
        """
        # 将半径和颜色作为键，用于在缓存中查找
        key = (int(radius), color)
        # 如果缓存中已经有了这个光晕图像，就直接返回，不再重新创建
        if key in self.glow_cache:
            return self.glow_cache[key]

        if radius < 2: radius = 2
        # 计算光晕图像的直径
        diameter = int(radius * 2)
        # 创建一个新的 Surface 对象，大小为直径x直径，SRCALPHA 表示支持像素级透明度
        glow_surface = pygame.Surface((diameter, diameter), pygame.SRCALPHA)
        """pygame.Surface(...)相当于一块空白的画布或一个图层。你可以在这个“画布”上绘制形状、填充颜色、或者blit（粘贴）其他的图像。
        2.传递给 pygame.Surface 的第一个参数，它定义了这块“画布”的尺寸（宽度和高度）
        3.第二个diameter 是一个变量，代表“直径”。所以 (diameter, diameter) 创建了一个正方形的画布，其宽度和高度都等于这个直径的值。
        4.pygame.SRCALPHA，意味着这个“画布”将支持**“逐像素 Alpha 通道，Alpha 通道是用来控制透明度的。一个像素的 Alpha 值决定了它有多么不透明。
        例如，Alpha = 255: 完全不透明。Alpha = 0: 完全透明（看不见）。Alpha = 1 到 254 之间的值: 半透明。SRCALPHA 允许你控制这个 Surface 上的每一个像素的透明度。"""
        # 从中心向外绘制，alpha值由高到低平滑衰减
        # 从外到内（或者说从大到小）画一系列同心圆来模拟光晕
        # 循环变量 i 代表当前要画的圆的半径
        for i in range(int(radius), 0, -1):
            # 使用指数衰减函数，模拟真实光照的衰减效果
            # 使用指数衰减函数计算当前半径圆的透明度 (alpha)
            # 这样中心最亮 (alpha接近255)，越往外越透明 (alpha接近0)
            alpha = int(255 * math.exp(- (i / (radius * 0.5)) ** 2))
            """1.高斯函数 math.exp(-x**2)这行代码的核心是高斯函数，其标准形式是 f(x)=e 的−x的 2次方
            2.math.exp(n): 这是 Python 的数学库函数，计算自然常数 e 的 n 次方
            3.图像是一个钟形曲线 (Bell Curve)。当输入为 0 时，math.exp(0) 结果为 1 (最大值)。
            当输入的绝对值越来越大时，结果会平滑地、迅速地趋近于 0。
            非常适合用来做“中心最亮，向外围逐渐变透明”的辉光效果
            4.i = 0 就是最中心
            5.radius: 这是你想要的光环效果的总半径。它定义了效果影响的范围，radius * 0.5: 这是一个调节参数，可以理解为**“有效半径”或“衰减速度控制器
             0.5 意味着当距离 i 达到总半径 radius 的一半时，衰减会变得非常明显。你可以通过调整这个 0.5 来改变光环的“软硬”程度。
            (如 0.2)，光环会更集中在中心，边缘更模糊。把它改大 (如 0.8)，光环会更宽，衰减得更慢。
            6.- ( ... ): 取负数，实现从中心向外逐渐变暗的效果。
            7.math.exp(...): 计算高斯函数的值。根据我们上面的分析，这个结果将在 0 和 1 之间。
            当 i=0 时，结果是 1.0。
            当 i 变大时，结果会平滑地接近 0。
            255 * ...: 将 0.0 到 1.0 的结果映射到 0 到 255 的颜色通道范围。
            8.int(...): 因为颜色值必须是整数，所以用 int() 来丢掉小数部分
            """
            # 如果透明度太低，就没必要画了
            if alpha < 1: continue
            # 在 glow_surface 上画一个圆
            # 参数: 画布, 颜色(带alpha), 中心点(画布中心), 半径
            pygame.draw.circle(glow_surface, (*color, alpha), (radius, radius), i)
        # 将新创建的光晕图像存入缓存
        self.glow_cache[key] = glow_surface
        # 返回创建好的光晕图像
        return glow_surface

    # 定义一个方法，用来创建一个星环，最小半径，最大半径，速度，粒子数
    def create_ring(self, min_rad, max_rad, speed, num_particles):
        # 创建一个字典来存储星环的属性
        ring = {'angle': 0, 'speed': speed, 'particles': []}#这是在创建一个字典。
        # 创建一个名为 ring 的字典，它有三个键：'angle'、'speed' 和 'particles'，并为它们分别赋予初始值
        # 根据指定的粒子数量循环
        for _ in range(num_particles):
            # 在星环的粒子列表中添加一个新的粒子（也是一个字典）
            """1.ring['particles']在访问 ring 这个字典中键为 'particles' 的值
            2..append() 是 Python 列表的一个内置方法（函数），作用是在列表的末尾添加一个新元素
            3.{...} 里面的内容 {'angle_offset': ..., 'radius': ..., 'color': ..., 'size': ...} 
            又是在创建一个新的字典。这个新字典代表了一个单独的粒子，包含了它自己的属性（角度偏移、半径、颜色、大小）。
            总结：找到 ring 字典里'particles'键对应的值（那个列表），然后调用这个列表的 append 方法，把一个代表新粒子的字典添加到这个列表的末尾"""
            ring['particles'].append({
                'angle_offset': random.uniform(0, 2 * math.pi),
                'radius': random.uniform(min_rad, max_rad),
                'color': random.choice(self.dark_palette),
                'size': random.uniform(2, 4)
            })
        # 返回创建好的星环字典
        return ring

    # 定义一个方法，重置 Logo 的位置
    def reset(self):
        # 将 Logo 的中心点 x 坐标设置到屏幕左侧外部，这样它就会从左边滑入
        self.center_x = -self.width / 2

    # 定义更新方法，每一帧都会被调用，用于更新 Logo 的状态
    def update(self):
        self.time += 1 # 计时器加1
        self.center_x += self.speed # 根据速度移动 Logo 的中心点 x 坐标
        if self.center_x - self.width > self.screen_width:
        # 如果 Logo 完全移出了屏幕右侧
        # 就调用 reset 方法，让它回到左侧重新开始
            self.reset()

        # 遍历每一个能量云粒子并更新它们的状态
        for wisp in self.wisps:
            # 根据速度更新粒子的相对位置
            wisp['rel_pos'] += wisp['vel']
            # 如果粒子碰到了效果区域的左右边界，就让它的水平速度反向（弹回）
            """边界碰撞检测
            1.核心思想：让粒子在一个看不见的“盒子”里来回反弹
            2.使用的坐标系是以效果区域的中心为原点 (0, 0) 的。self.width: 是这个盒子的总宽度。self.height: 是这个盒子的总高度。
            3.这个盒子的边界是：
            左边界的 x 坐标是 -self.width / 2
            右边界的 x 坐标是 +self.width / 2
            上边界的 y 坐标是 -self.height / 2 (在很多图形学坐标系中，y轴向下为正)
            下边界的 y 坐标是 +self.height / 2"""
            if not (-self.width / 2 < wisp['rel_pos'].x < self.width / 2): wisp['vel'].x *= -1
            """
            1.(-self.width / 2 < wisp['rel_pos'].x < self.width / 2)这是一个Python的链式比较。
            它的意思是,检查粒子的x坐标 wisp['rel_pos'].x 是否在 -self.width / 2 和 self.width / 2 之间
            2.not 关键字:not 会把后面的判断结果取反。
            如果粒子在盒子内部，(...) 的结果是 True，not (...) 的结果就是 False。
            如果粒子不在盒子内部（也就是碰到了左右边界或者超出了边界），(...) 的结果是 False，not (...) 的结果就变成了 True
            3.整个 if 条件的意思是：“如果粒子在水平方向上已经不在盒子内部了（碰到了左右墙壁
            4.wisp['vel'].x *= -1:
            这是当 if 条件为 True 时执行的动作。wisp['vel'].x 是粒子在x方向（水平方向）的速度。*= -1 的意思是把它自身乘以-1。
            5.如果速度是正数（向右运动），乘以-1后就变成负数（向左运动）。如果速度是负数（向左运动），乘以-1后就变成正数（向右运动）反弹效果
            6.
            """
            # 如果粒子碰到了效果区域的上下边界，就让它的垂直速度反向（弹回）
            if not (-self.height / 2 < wisp['rel_pos'].y < self.height / 2): wisp['vel'].y *= -1
            """
            同上，粒子的高度不在盒子里，速度反向
            """
            # 增加脉冲效果的时间变量
            wisp['pulse_time'] += 0.08
            """脉冲效果 :一种快速、有节奏的闪烁、放大缩小或变亮变暗
            2.在每一帧（每一次游戏循环）都会给 pulse_time 这个变量增加 0.08。这个数字 0.08 决定了脉冲的速度，
            数值越大，脉冲越快
            sin(wisp['pulse_time']) 的值会在 -1 和 1 之间平滑地来回振荡，从而让 current_size 也跟着有节奏地变大变小，形成“脉冲”的视觉效果"""
            # 增加呼吸效果的时间变量
            wisp['breath_time'] += 0.01
            """呼吸效果:通常指一种缓慢、平滑、柔和的变亮变暗或变大变小。
            2. sin 函数中时（例如 current_alpha = base_alpha + sin(wisp['breath_time']) * 0.5），产生的振荡周期就会非常长，变化非常缓慢和柔和，看起来就像在“呼吸”
            """
        # 遍历每一个星环并更新它们的状态
        for ring in self.rings:
            # 根据速度更新整个星环的旋转角度
            ring['angle'] += ring['speed']
        # 每隔 300 帧清理一次光晕缓存，防止内存占用过大
        if self.time % 300 == 0:
            self.glow_cache.clear()

    # 定义绘制方法，每一帧调用，把 Logo 画到屏幕上
    def draw(self, screen):
        # 计算当前 Logo 中心的绝对屏幕坐标
        current_center = pygame.Vector2(self.center_x, self.y_pos)

        # --- 绘制星环 ---
        # --- 绘制银河粒子环 ,遍历所有星环
        for ring in self.rings:
            # 遍历该星环中的每一个粒子
            for p in ring['particles']:
                # 计算粒子当前在圆周上的总角度
                angle = ring['angle'] + p['angle_offset']
                # 使用三角函数计算粒子的 x, y 坐标（sin后面乘以0.6是为了让椭圆看起来扁一些，有立体感）
                pos_x = current_center.x + p['radius'] * math.cos(angle)
                """把极坐标（Polar Coordinates） 转换成了我们通常在屏幕上使用的笛卡尔坐标（Cartesian Coordinates），并且做了一点小小的变化
                1.p['radius'] 是这个粒子距离中心点的半径或距离
                2.半径乘以 cos(angle)，就相当于把单位圆的坐标“放大”到了我们实际需要的圆上
                3.p['radius'] * math.cos(angle) 代表的就不再是单位圆上的 x 坐标了，而是相对于圆心，在水平方向上需要移动的偏移量
                """
                pos_y = current_center.y + p['radius'] * math.sin(angle) * 0.6
                """
                1.如果没有 * 0.6，那么粒子的运动轨迹将是一个正圆形。
                * 0.6: 这个操作的意思是，将计算出的垂直偏移量乘以 0.6，也就是把它压缩（或缩放）到原来的 60%。
                2.相当于，水平方向的移动距离保持不变，垂直方向的移动距离被“压扁”了，，粒子的运动轨迹就不再是一个正圆形，而是一个椭圆形（Ellipse）。
                这个椭圆在水平方向上更宽，在垂直方向上更窄
                """
                # 组合成坐标元组
                pos = (pos_x, pos_y)
                # 为这个粒子创建一个光晕图像
                glow_img = self.create_aurora_glow(p['size'] * 1.5, p['color'])
                # 把光晕图像绘制到屏幕上，使用 BLEND_RGBA_ADD 混合模式，产生发光、叠加的效果
                screen.blit(glow_img, glow_img.get_rect(center=pos), special_flags=pygame.BLEND_RGBA_ADD)
                """
                1.screen: 这通常是你的主游戏窗口，是你最终看到所有画面的那个“画布
                2.blit(...): 这是“绘制”或“粘贴”的动作。这行代码的第一部分意思就是：“准备把 glow_img 这张图画到 screen（屏幕）上去。
                3.glow_img.get_rect(): 在 Pygame 中，每个图像（Surface）都有一个看不见的“外框”，叫做 Rect (矩形)。
                这个 Rect 对象存储了图像的位置（x, y 坐标）和尺寸（宽度、高度）。get_rect() 这个函数就是获取 glow_img 这个图像的矩形外框
                4.special_flags: 这是一个可选参数，用来指定一种特殊的绘制模式,pygame.BLEND_RGBA_ADD: 这是一种加色混合模式
                5.它不会用 glow_img 的像素去覆盖屏幕上原来的像素。
                而是将 glow_img 的每个像素的颜色值（R, G, B, A）与屏幕上对应位置的像素颜色值相加。
                结果就是：颜色会混合，并且亮度会叠加。黑色（颜色值为0）的地方加上去等于没变化，越亮的地方加上去就会变得更亮，
                甚至会变成纯白色（当颜色值加到最大时）
                """

        # --- 绘制主要的能量云 ---
        # 遍历所有能量云粒子
        for wisp in self.wisps:
            # 计算能量云的绝对屏幕坐标
            pos = current_center + wisp['rel_pos']
            # 使用 sin 函数根据时间变量计算出“呼吸”效果的半径变化
            breath_radius = wisp['base_radius'] + math.sin(wisp['breath_time']) * 3
            # 在呼吸效果的基础上，再用 sin 函数计算出频率更快的“脉冲”效果的半径变化
            pulse_radius = breath_radius + math.sin(wisp['pulse_time']) * 1.5
            # 获取粒子的颜色
            color = wisp['color']
            # 根据最终计算出的半径和颜色，创建光晕图像
            glow_img = self.create_aurora_glow(pulse_radius * 2.5, color)
            screen.blit(glow_img, glow_img.get_rect(center=pos), special_flags=pygame.BLEND_RGBA_ADD)

        # --- 绘制固定的姓名和学号 ---
        # 计算姓名的位置（Logo中心点偏上）
        name_pos = (current_center.x, current_center.y - 15)
        # 计算学号的位置（Logo中心点偏下）
        id_pos = (current_center.x, current_center.y + 15)
        # 将之前渲染好的姓名文字图像绘制到屏幕上
        screen.blit(self.name_surf, self.name_surf.get_rect(center=name_pos))
        # 将之前渲染好的学号文字图像绘制到屏幕上
        screen.blit(self.id_surf, self.id_surf.get_rect(center=id_pos))